using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Threading;
using System.Windows.Forms;

namespace SBPweb.Localization
{
	/// <summary>
	/// Class of LocalizationExtender
	/// </summary>
	[Category("Localization")]
	[ProvideProperty("ResourceKey", typeof(Control))]
	public class LocalizationExtender : Component, IExtenderProvider
	{
		#region Private members

		private static IDictionary<Type, Assembly> assemblies = new Dictionary<Type, Assembly>();
		private static IDictionary<Assembly, IDictionary<CultureInfo, ResourceSet>> resourceSets = new Dictionary<Assembly, IDictionary<CultureInfo, ResourceSet>>();
		private static IDictionary<Component, string> resourceKeys = new Dictionary<Component, string>();
		private static IDictionary<Type, ComponentResourceManager> resourceManagers = new Dictionary<Type, ComponentResourceManager>();
		private static List<Component> pContainerList = null;
		private const string mMissingResource = "[Missing resource value or wrong key]";

		private static CultureInfo mCulture;

		#endregion

		#region Constructor logic

		static LocalizationExtender()
		{
		}

		#endregion

		#region IExtenderProvider Members

		/// <summary>
		/// Can extend?
		/// </summary>
		/// <param name="extendee">object</param>
		/// <returns>true or false</returns>
		public bool CanExtend(object extendee)
		{
			return true;
		}

		#endregion

		#region ResourceKey accessors

		/// <summary>
		/// Get resource key for control
		/// </summary>
		/// <param name="control">control</param>
		/// <returns>string</returns>
		[Category("Localization")]
		public string GetResourceKey(Component control)
		{
			if (!resourceKeys.ContainsKey(control) && control is Control)
				return (control as Control).Name;
			return resourceKeys[control];
		}

		/// <summary>
		/// Set resource key for control
		/// </summary>
		/// <param name="control">control</param>
		/// <param name="value">value</param>
		[Category("Localization")]
		public void SetResourceKey(Component control, string value)
		{
			resourceKeys[control] = value;
			assemblies[control.GetType()] = Assembly.GetCallingAssembly();

			Invalidate(control, value);
		}

		#endregion

        #region Public property accessors

		/// <summary>
		/// Current Culture
		/// </summary>
        public static CultureInfo Culture
        {
            get
            {
                if (mCulture == null)
                {
                    mCulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
                }
                return mCulture;
            }
            set
            {
                mCulture = value;
                InvalidateControls();
            }
        }

        #endregion

        #region Public methods

		/// <summary>
		/// Generate the container list
		/// </summary>
        public static void GenerateContainerList()
		{
			if (pContainerList == null)
			{
				pContainerList = new List<Component>();
			}
			pContainerList.Clear();
			Control container = null;
			foreach (Component comp in resourceKeys.Keys)
			{
				Control ctrl = comp as Control;
				if (ctrl != null)
				{
					container = ctrl.Parent;
					while (container != null)
					{
						if (!pContainerList.Contains(container))
						{
							pContainerList.Add(container);
							container = container.Parent as Control;
						}
						else
						{
							container = null;
						}
					}
				}
			}
		}

		/// <summary>
		/// Get string
		/// </summary>
		/// <param name="key">key</param>
		/// <returns>string</returns>
		public static string GetString(string key)
		{
			Assembly asm = Assembly.GetCallingAssembly();
			return GetString(asm, "Resources.Texts", key);
		}

		/// <summary>
		/// Get string
		/// </summary>
		/// <param name="filePartialPath">file path</param>
		/// <param name="key">key</param>
		/// <returns>string</returns>
		public static string GetString(string filePartialPath, string key)
		{
			Assembly asm = Assembly.GetCallingAssembly();
			return GetString(asm, filePartialPath, key);
		}

		/// <summary>
		/// Get object
		/// </summary>
		/// <param name="key">key</param>
		/// <returns>object</returns>
		public static object GetObject(string key)
		{
			Assembly asm = Assembly.GetCallingAssembly();
			return GetObject(asm, "Resources.Texts", key);
		}

		/// <summary>
		/// Get object
		/// </summary>
		/// <param name="filePartialPath">file path</param>
		/// <param name="key">key</param>
		/// <returns>object</returns>
		public static object GetObject(string filePartialPath, string key)
		{
			Assembly asm = Assembly.GetCallingAssembly();
			return GetObject(asm, filePartialPath, key);
		}

		/// <summary>
		/// Get string for control
		/// </summary>
		/// <param name="control">control</param>
		/// <param name="key">key</param>
		/// <returns>string</returns>
		public static string GetString(Component control, string key)
		{
			if(control is Control && IsManagerAvailable(control as Control))
			{
				ComponentResourceManager crm = InitializeAndGetManager(control.GetType());

				string returnValue = crm.GetString(key);

				if (returnValue != null)
				{
					return returnValue;
				}
				else
				{
					return mMissingResource;
				}
			}
			else
			{
				return mMissingResource;
				//return null;
			}
		}

		/// <summary>
		/// Get object
		/// </summary>
		/// <param name="control">control</param>
		/// <param name="key">key</param>
		/// <returns>object</returns>
		public static object GetObject(Component control, string key)
		{
			if (control is Control && IsManagerAvailable(control as Control))
			{
				ComponentResourceManager crm = InitializeAndGetManager(control.GetType());

				return crm.GetObject(key, Culture);
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Get unmaaged memory stream
		/// </summary>
		/// <param name="control">control</param>
		/// <param name="key">key</param>
		/// <returns>stream</returns>
		public static UnmanagedMemoryStream GetStream(Component control, string key)
		{
			if (control is Control && IsManagerAvailable(control as Control))
			{
				ComponentResourceManager crm = InitializeAndGetManager(control.GetType());

				return crm.GetStream(key, Culture);
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Start to invalidate the controls
		/// </summary>
        public static void InvalidateControls()
        {
            GenerateContainerList();
            foreach (Control container in pContainerList)
            {
                container.SuspendLayout();
            }
            if (OnInvalidatingControls != null)
            {
                OnInvalidatingControls(resourceKeys, EventArgs.Empty);
            }
            foreach (Component ctrl in resourceKeys.Keys)
            {
                Invalidate(ctrl, resourceKeys[ctrl]);
            }
            if (OnInvalidatedControls != null)
            {
                OnInvalidatedControls(resourceKeys, EventArgs.Empty);
            }
            foreach (Control container in pContainerList)
            {
                container.ResumeLayout(true);
            }
        }

        #endregion

		#region Public events

		/// <summary>
		/// Raised before invalidation of controls
		/// </summary>
		public static event EventHandler OnInvalidatingControls;
		/// <summary>
		/// Raised when all controls are invalidated
		/// </summary>
		public static event EventHandler OnInvalidatedControls;

		#endregion

		#region Private helpers

		private static void Invalidate(Component obj, string key)
		{
			Control control = obj as Control;
			if (control == null)
			{
				Type typ = obj.GetType();
				PropertyInfo pI = typ.GetProperty("Parent", BindingFlags.Instance | BindingFlags.NonPublic);

				//TODO: Try finding the first possible control as a parent recursively
				control = (Control)(pI.GetValue(obj, null));
				if (control == null)
				{
					return;
				}
			}

			Type type = control.GetType();

			Control parent = control;

			while (!IsManagerAvailable(parent) && (parent.Parent != null))
			{
				parent = parent.Parent;
			}

			if (!IsManagerAvailable(parent))
			{
				return;
			}

			ComponentResourceManager crm = InitializeAndGetManager(parent.GetType());

			try
			{
				crm.ApplyResources(obj, key, Culture);
				control.Invalidate();
				return;
			}
			catch
			{
			}

			control.Invalidate();
		}

		private static bool IsManagerAvailable(Control control)
		{
			Type type = control.GetType();
			if (!assemblies.ContainsKey(type))
			{
				return false;
			}
			Assembly asm = assemblies[type];
			return type.Assembly == asm;
		}

		private static ComponentResourceManager InitializeAndGetManager(Type type)
		{
			if (resourceManagers.ContainsKey(type))
			{
				return resourceManagers[type];
			}
			
			ComponentResourceManager ret = new ComponentResourceManager(type);
			resourceManagers.Add(type, ret);

			return ret;
		}

		private static string GetString(Assembly asm, string filePartialPath, string key)
		{
			IDictionary<CultureInfo, ResourceSet> dict;
			if (resourceSets.ContainsKey(asm))
			{
				dict = resourceSets[asm];
				if (dict.ContainsKey(Culture))
				{
					return dict[Culture].GetString(key);
				}
			}
			else
			{
				dict = new Dictionary<CultureInfo, ResourceSet>();
				resourceSets.Add(asm, dict);
			}

			Assembly satAsm = asm.GetSatelliteAssembly(Culture);

			string[] names = satAsm.GetManifestResourceNames();
			string name = string.Empty;
			foreach (string n in names)
			{
				if (n.IndexOf(filePartialPath) >= 0)
				{
					name = n;
				}
			}

			Stream stream = satAsm.GetManifestResourceStream(name);
			ResourceReader resourceReader = new ResourceReader(stream);
			ResourceSet rs = new ResourceSet(resourceReader);

			dict.Add(Culture, rs);

			string returnValue = rs.GetString(key);
			
			if (returnValue != null)
			{
				return returnValue;
			}
			else
			{
				return mMissingResource;
			}
		}

		private static object GetObject(Assembly asm, string filePartialPath, string key)
		{
			IDictionary<CultureInfo, ResourceSet> dict;
			if (resourceSets.ContainsKey(asm))
			{
				dict = resourceSets[asm];
				if (dict.ContainsKey(Culture))
				{
					return dict[Culture].GetObject(key);
				}
			}
			else
			{
				dict = new Dictionary<CultureInfo, ResourceSet>();
				resourceSets.Add(asm, dict);
			}

			Assembly satAsm = asm.GetSatelliteAssembly(Culture);

			string[] names = satAsm.GetManifestResourceNames();
			string name = string.Empty;
			foreach (string n in names)
			{
				if (n.IndexOf(filePartialPath) >= 0)
				{
					name = n;
				}
			}

			Stream stream = satAsm.GetManifestResourceStream(name);
			ResourceReader resourceReader = new ResourceReader(stream);
			ResourceSet rs = new ResourceSet(resourceReader);

			dict.Add(Culture, rs);

			return rs.GetObject(key);
		}

		#endregion
	}
}
